<!DOCTYPE html>
<!--
Copyright 2010 WebDriver committers
Copyright 2010 Google Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<html>
<head>
  <title>locator_test.html</title>
  <script src="test_bootstrap.js"></script>
  <script type="text/javascript">
    goog.require('bot');
    goog.require('bot.ErrorCode');
    goog.require('bot.locators');
    goog.require('goog.events.EventType');
    goog.require('goog.testing.PropertyReplacer');
    goog.require('goog.testing.jsunit');
  </script>

  <script type="text/javascript">
    var CAN_SELECT_SINGLE_NODE =
        document.documentElement['selectSingleNode'] || document['evaluate'];
    var CAN_SELECT_MANY_NODES =
        document.documentElement['selectNodes'] || document['evaluate'];

    function testCanFindById() {
      var e = bot.locators.findElement({id: 'x'});

      assertEquals('para', e.getAttribute('name'));
    }

    function testCanFindByIdWhenElementIsAfterOneWithSameName() {
      var e = bot.locators.findElement({id: 'after'});

      assertEquals('right', e.getAttribute('name'));
    }

    function testFindingByNameIgnoresComments() {
      // This element is outside a form, forcing IE to fall back to using
      // doc.getElementsByTagName. There are comments on this page that are
      // returned in this collection. We access the "attributes" array of
      // each element, which is clearly missing from comments. A roundabout way
      // of doing this, but it works

      try {
        var element = bot.locators.findElement({name: 'para'});
        assertNotNull(element); // this is expected
      } catch (e) {
        fail(e);  // this is not.
      }
    }

    function testCanFindByNameOutsideOfAForm() {
      var e = bot.locators.findElement({name: 'para'});

      assertEquals('x', e.getAttribute('id'));
    }

    function testWillNotFindAnElementByNameWhereNoMatchShouldBeFound() {
      assertNull(bot.locators.findElement({name: 'foobar'}));
    }

    function testCanFindByClassName() {
      var dogs = bot.locators.findElement({className: 'dogs'});
      assertNotNull(dogs);
      assertEquals('after', dogs.id);
    }

    function testCannotSearchWithCompoundClassNames() {
      assertThrows(goog.bind(bot.locators.findElement, null,
          {className: 'feline cats'}));
    }

    function testFindByClassNameReturnsFirstMatchingElement() {
      var cats = bot.locators.findElement({className: 'cats'});
      assertNotNull(cats);
      assertEquals('wrong', cats.id);
    }

    function testCanFindByAnyOfAnElementsClassNames() {
      var felines = bot.locators.findElement({className: 'feline'});
      var cats = bot.locators.findElement({className: 'cats'});

      assertEquals(felines, cats);
      assertEquals('wrong', cats.id);
    }

    function testFindByClassNameReturnsNullIfNoMatchIsFound() {
      assertNull(bot.locators.findElement({className: 'catsAndDogs'}));
    }

    /** @bug http://code.google.com/p/selenium/issues/detail?id=1918 */
    function testFindSingleByClassName_targetClassHasDots() {
      var found = bot.locators.findElement({className: 'name.with.dots'});
      assertEquals(goog.dom.$('dotted_1'), found);
    }

    /** @bug http://code.google.com/p/selenium/issues/detail?id=1918 */
    function testFindManyByClassName_targetClassHasDot() {
      var found = bot.locators.findElements({className: 'name.with.dots'});
      assertEquals(2, found.length);
      assertEquals(goog.dom.$('dotted_1'), found[0]);
      assertEquals(goog.dom.$('dotted_2'), found[1]);
    }

    function testCanFindElementByCssSelector() {
      if (!document['querySelectorAll']) {
        return;  // Skip this until we get selectors working on all browsers
      }

      var after = bot.locators.findElement({css: '#wrong'});

      assertNotNull(after);
      assertEquals('wrong', after.id);
    }

    function testShouldReturnNullIfNoCssMatchIsFound() {
      if (!document['querySelectorAll']) {
        return;  // Skip this until we get selectors working on all browsers
      }

      assertNull(bot.locators.findElement({css: '#uglyfish'}));
    }

    function testWillRejectShouldRejectMultipleSelectors() {
      assertThrows(goog.bind(bot.locators.findElement, null,
          {css: '.one, #two'}));
    }

    function testShouldFindElementWithCommaInAttribute() {
      assertNotNull(goog.bind(bot.locators.findElement, null,
          {css: 'comma-in-alt[alt="has, a comma"]'}));
    }

    function testCanLocateElementsUsingXPath() {
      if (!CAN_SELECT_SINGLE_NODE) {
        return;  // Skip this until we get xpath working on all browsers
      }

      var doggies = bot.locators.findElement({xpath: "//*[@id = 'after']"});

      assertNotNull(doggies);
      assertEquals('after', doggies.id);
    }

    function testWillReturnNullIfNoMatchUsingXPathIsFound() {
      if (!CAN_SELECT_SINGLE_NODE) {
        return;  // Skip this until we get xpath working on all browsers
      }

      assertNull(bot.locators.findElement({xpath: '//fish'}));
    }

    function testShouldThrowInvalidSelectorErrorWhenXPathIsSyntacticallyInvalidInSingle() {
      if (!CAN_SELECT_SINGLE_NODE) {
        return;  // Skip this until we get xpath working on all browsers
      }

      try {
        bot.locators.findElement({xpath: 'this][isnot][valid'});
        fail('Should not have succeeded because the xpath expression is ' +
                    'syntactically not correct');
      } catch (ex) {
        //We expect an InvalidSelectorException because the xpath expression is
        //syntactically invalid.
        assertEquals(bot.ErrorCode.INVALID_SELECTOR_ERROR, ex.code);
      }
    }

    function testShouldThrowInvalidSelectorErrorWhenXPathReturnsAWrongTypeInSingle() {
      if (!CAN_SELECT_SINGLE_NODE) {
        return;  // Skip this until we get xpath working on all browsers
      }

      try {
        bot.locators.findElement({xpath: 'count(//fish)'});
        fail('Should not have succeeded because the xpath expression does ' +
                    'not select an element.');
      } catch (ex) {
        // We expect an exception because the XPath expression
        // results in a number, not in an element.
        assertEquals(bot.ErrorCode.INVALID_SELECTOR_ERROR, ex.code);
      }
    }

    function testCanFindMoreThanOneElementByName() {
      var allFoos = bot.locators.findElements({name: 'foo'});

      assertEquals(2, allFoos.length);
    }

    function testCanFindManyElementsUsingCss() {
      if (!document['querySelectorAll']) {
        return;  // Skip this until we get selectors working on all browsers
      }

      var cats = bot.locators.findElements({css: '.cats'});

      assertEquals(3, cats.length);
    }

    function testCanFindManyElementsUsingClassName() {
      var cats = bot.locators.findElements({className: 'cats'});

      assertEquals(3, cats.length);
    }

    function testCanFindManyElementsUsingAnId() {
      var bad = bot.locators.findElements({id: 'illegal'});

      assertEquals(4, bad.length);
    }

    function testCanFindManyElementsViaXPath() {
      if (!CAN_SELECT_MANY_NODES) {
        return;  // Skip this until we get xpath working on all browsers
      }

      var bad = bot.locators.findElements({xpath: '//*[@name = "after"]'});

      assertEquals(2, bad.length);
    }

    function testShouldThrowInvalidSelectorErrorWhenXPathIsSyntacticallyInvalidInMany() {
      if (!CAN_SELECT_MANY_NODES) {
        return;  // Skip this until we get xpath working on all browsers
      }

      try {
        bot.locators.findElements({xpath: 'this][isnot][valid'});
        fail('Should not have succeeded because the xpath expression is ' +
            'syntactically not correct.');
      } catch (ex) {
        // We expect an InvalidSelectorException because the xpath expression is
        // syntactically invalid.
        assertEquals(bot.ErrorCode.INVALID_SELECTOR_ERROR, ex.code);
      }
    }

    function testShouldThrowInvalidSelectorErrorWhenXPathReturnsAWrongTypeInMany() {
      if (!CAN_SELECT_MANY_NODES) {
        return;  // Skip this until we get xpath working on all browsers
      }

      try {
        bot.locators.findElements({xpath: 'count(//fish)'});
        fail('Should not have succeeded because the xpath expression does ' +
            'not select an element.');
      } catch (ex) {
        // We expect an exception because the XPath expression
        // results in a number, not in an element.
        assertEquals(bot.ErrorCode.INVALID_SELECTOR_ERROR, ex.code);
      }
    }

    function testCanFindElementByLinkText() {
      var link = bot.locators.findElement({linkText: 'this is a link'});

      assertEquals('link', bot.dom.getAttribute(link, 'id'));
    }

    function testCanFindElementsByLinkText() {
      var links = bot.locators.findElements({linkText: 'this is a link'});

      assertEquals(5, links.length);
    }

    function testShouldBeAbleToFindLinksWithNoText() {
      var link = bot.locators.findElement({linkText: ''});
      assertNotNull(link);
      assertEquals('empty-link', link.id);

      var links = bot.locators.findElements({linkText: ''});
      assertEquals(1, links.length);
      assertEquals('empty-link', links[0].id);
    }

    function testCanFindElementByPartialLinkText() {
      var link = bot.locators.findElement({partialLinkText: 'is a'});

      assertEquals('link', bot.dom.getAttribute(link, 'id'));
    }

    function testCanFindElementsByPartialLinkText() {
      var links = bot.locators.findElements({partialLinkText: 'a lin'});

      assertEquals(5, links.length);
    }

    function testShouldMatchFirstLinkWhenLinkTextIsTheEmptyString() {
      var link = bot.locators.findElement({partialLinkText: ''});
      assertNotNull(link);
      assertEquals('link', link.id);
    }

    function testShouldFindEveryLinkWhenLinkTextIsTheEmptyString() {
      var links = bot.locators.findElements({partialLinkText: ''});
      assertEquals(8, links.length);
    }

    function testShouldBeAbleToFindElementByTagName() {
      var link = bot.locators.findElement({tagName: 'A'});
      assertNotNull(link);
      assertEquals('link', link.id);
    }

    function testShouldBeAbleToFindElementsByTagName() {
      var links = bot.locators.findElements({tagName: 'A'});
      assertEquals(8, links.length);
    }

    function testCanAddANewElementLocatingStrategy() {
      var expected = goog.dom.$('lion');
      bot.locators.add('fixed', {
        single: function() {
          return expected;
        }
      });

      var seen = bot.locators.findElement({fixed: 'ignored'});

      assertEquals(expected, seen);
    }

    function testShouldSurviveObjectPrototypeBeingModified() {
      Object.prototype.cheese = 'brie';
      Object.prototype.Inherits = function() { /* does nothing */ };

      var first = bot.locators.findElement({name: 'tiger'});
      assertEquals('tiger', first.getAttribute('name'));

      var locator = new Object();
      locator.name = 'tiger';

      var second = bot.locators.findElement(locator);
      assertEquals('tiger', second.getAttribute('name'));
    }

    function testShouldReturnNullIfXPathResolverThrowsNsError_Gecko() {
      if (!goog.userAgent.GECKO) {
        return;
      }

      var stubs = new goog.testing.PropertyReplacer();
      // This simulates what happens in a firefox extension if the page
      // reloads as an xpath query is being run.
      stubs.set(document, 'createNSResolver', function() {
        var error = new Error();
        error.name = 'NS_ERROR_ILLEGAL_VALUE';
        throw error;
      });

      try {
        var result = bot.locators.findElement({xpath: '//body'});
        assertNull(result);

        var all = bot.locators.findElements({xpath: '//body'});
        assertEquals(0, all.length);
      } finally {
        stubs.reset();
      }
    }

</script>
</head>
<body>
  <p id="x" name="para">Para</p>

  <div name="after" id="wrong" class="feline cats">nope</div>
  <div name="right" id="after" class="dogs">yup</div>
  <div name="lion" class="cats">simba</div>
  <div name="tiger" class="cats">shere khan</div>
  <div id="dotted_1" class="name.with.dots">dotted class</div>
  <div id="dotted_2" class="name.with.dots">another dotted class</div>

  <form action="#">
    <input name="after" /><br />
    <input name="foo" />
  </form>

  <!-- This comment should be ignored -->

  <span name="foo">Furrfu</span>

  <ul>
    <li id="illegal">item
    <li id="illegal">item
    <li id="illegal">item
    <li id="illegal">item
  </ul>

  <a id="link" href="#">this is a link</a>
  <a name="fishsticks">this is a link</a>
  <a href="#">this is a link</a>
  <a href="#">this is a link</a>
  <a href="#">this is a link</a>

  <a href="#">unrelated</a>
  <a href="#" id="empty-link"></a>
  <a href="#" id="comma-in-alt" alt="has, a comma">has, a comma</a>
  </body>
</html>
